/**
 * 
 */
package org.codesketch.controller.schedule;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.codesketch.birch.command.Birch;
import org.codesketch.birch.command.RequestField;
import org.codesketch.birch.common.BirchRequest;
import org.codesketch.birch.common.BirchResponse;
import org.codesketch.birch.common.Instance;
import org.codesketch.birch.ssh.File;
import org.codesketch.birch.ssh.Response;
import org.codesketch.birch.ssh.ShellStatement;
import org.codesketch.birch.ssh.UploadStatement;
import org.codesketch.controller.common.Preconditions;
import org.codesketch.controller.dao.ApplicationDao;
import org.codesketch.controller.domain.Application;
import org.codesketch.controller.domain.Environment;
import org.codesketch.controller.domain.Test;
import org.codesketch.controller.exception.TestExecutionException;
import org.codesketch.controller.exception.UnprocessabeEntityException;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;

import com.amazonaws.services.ec2.model.Reservation;

/**
 * Executes all the necessary steps to get a {@link Test} up and running
 * @author quirino.brizi@gmail.com
 *
 */
@Configurable(autowire=Autowire.BY_TYPE)
public class TestRunner {
	
	@Autowired
	private transient ApplicationDao applicationDao;
	@Autowired
	private transient Birch birch;
	
	/**
	 * Execute the requested {@link Test}.
	 * @param test the test definition.
	 * @throws TestExecutionException if any error occur during the test 
	 * 		   configuration and running
	 * @throws UnprocessabeEntityException if any entity needed for test 
	 * 		   configuration and scheduling is not available.
	 */
	public void run(Test test) 
			throws TestExecutionException, UnprocessabeEntityException {
		Preconditions.checkNullThrowsUnprocessableException(test);
		test.setResource(application(test.getApplication()));
		test.setResource(configuration(test));
		start(test.getEnvironment());
		install(test);
		execute(test);
	}
	/**
	 * Load the testing application as a {@link File}. 
	 * @param app the testing application definition
	 * @return the test configuration as a {@link File}
	 */
	File application(Application app) {
		Preconditions.checkNullThrowsUnprocessableException(app);
		URI source = app.getSource();
		Preconditions.checkNullThrowsUnprocessableException(source);
		InputStream stream = applicationDao.find(source);
		checkValidThrowTestExecutionException(stream, 
				String.format("Unable loading application stream from " +
						"source {%s}", source.toASCIIString()));
		return File.with(app.getName(), stream);
	}
	/**
	 * Prepare the test configuration and return it as a {@link File}
	 * @param test the test definition
	 * @return the test configuration as a {@link File}
	 */
	File configuration(Test test) {
		// TODO implement me
		InputStream stream = new ByteArrayInputStream(new byte[0]);
		checkValidThrowTestExecutionException(stream, 
				"Unable generating application configuration stream");
		return File.with("configuration.xml", stream);
	}
	/**
	 * Start the requested number of machines on AWS cloud. The list of 
	 * instances started on AWS is added to the given {@link Environment} 
	 * definition parameter.
	 * @param env the AWS environment definition
	 */
	void start(Environment env) {
		Preconditions.checkNullThrowsUnprocessableException(env);
		
		BirchRequest request = new BirchRequest();
		request.put(RequestField.RUN_INSTANCE_MIN_COUNT, 
				env.getNumOfInstance());
		request.put(RequestField.RUN_INSTANCE_MAX_COUNT, 
				env.getNumOfInstance());
		request.put(RequestField.RUN_INSTANCE_IMAGE_ID, env.getImageId());
		request.put(RequestField.RUN_INSTANCE_INSTANCE_TYPE, 
				env.getInstanceType());
		BirchResponse<Reservation> response = birch.run(request);
		if(!response.getTransitionValid()) {
			throw new TestExecutionException(
					"Unable to prepare AWS environment");
		}
		Reservation reservation = response.getResponse();
		checkValidThrowTestExecutionException(reservation, 
				"Unable to configure AWS environment, invalid reservation.");
		List<Instance> instances = new ArrayList<Instance>();
		for(com.amazonaws.services.ec2.model.Instance instance : 
			reservation.getInstances()) {
			instances.add(Instance.with(instance, env.getUsername(), 
					env.getPrivateKey()));
		}
		checkValidThrowTestExecutionException(reservation, 
				"Unable to configure AWS environment, invalid or empty " +
				"reservation.");
		env.setInstances(instances);
	}
	/**
	 * Install the test application and resources on AWS environment.
	 * @param test the test specification container.
	 */
	void install(Test test) {
		List<Instance> instances = test.getInstances();
		for(Instance instance : instances) {
			UploadStatement statement = UploadStatement.with(
					test.getResources());
			Response<String> response = instance.upload(statement);
			checkValidThrowTestExecutionException(response, 
					"Unable deploy resource on AWS instance");
			/*
			 * Assume there is only one application to install, all the 
			 * other files are resources for the application.
			 */
			ShellStatement install = test.getInstall();
			response = instance.execute(install);
			checkValidThrowTestExecutionException(response, 
					String.format("Unable install application on AWS " +
							"instance {%s} using command {%s}", 
							instance.getInstanceId(), install.statement()));
		}
	}
	
	void execute(Test test) {
		List<Instance> instances = test.getInstances();
		for(Instance instance : instances) {
			ShellStatement run = test.getRun();
			Response<String> response = instance.execute(run);
			checkValidThrowTestExecutionException(response, 
					String.format("Unable running test on AWS instance {%s} " +
							"using command {%s}", instance.getInstanceId(), 
							run.statement()));
		}
	}
	
	/**
	 * Helper method, throws {@link TestExecutionException} if the 
	 * input parameter is null.
	 * @param obj the object under test.
	 */
	<T> void checkValidThrowTestExecutionException(T obj, 
			String message) {
		if(Preconditions.checkNull(obj)) {
			throw new TestExecutionException(message);
		}
		if(obj instanceof Collection<?>) {
			Collection<?> collection = (Collection<?>) obj;
			if(collection.isEmpty()) {
				throw new TestExecutionException(message);
			}
		}
		if(obj instanceof Response<?>) {
			Response<?> resp = (Response<?>) obj;
			if(resp.getExitCode() > 0) {
				throw new TestExecutionException(message);
			}
		}
	}
}
